package article_dao

import (
	"bbs-web/internal/repository/dao"
	"context"
	"errors"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type ArticleDAO interface {
	GetByAuthor(ctx context.Context, author int64, offset, limit int) ([]dao.ArticleMode, error)
	UpdateById(ctx context.Context, d dao.ArticleMode) error
	Insert(ctx context.Context, d dao.ArticleMode) (int64, error)
	Sync(ctx context.Context, d dao.ArticleMode) (int64, error)
	Upsert(ctx context.Context, d dao.PublishArticleModels) error
	SyncStatus(ctx context.Context, author, id int64, status uint8) error
	GetById(ctx context.Context, id int64) (dao.ArticleMode, error)
	GetPubById(ctx context.Context, id int64) (dao.PublishArticleModels, error)
	ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]dao.ArticleMode, error)
}

type gormArticleDao struct {
	db *gorm.DB
}

func NewGormArticleDao(db *gorm.DB) ArticleDAO {
	return &gormArticleDao{db: db}
}

func (a *gormArticleDao) GetById(ctx context.Context, id int64) (dao.ArticleMode, error) {
	var arts dao.ArticleMode
	err := a.db.WithContext(ctx).Model(&dao.ArticleMode{}).Where("id = ?", id).First(&arts).Error
	return arts, err
}

func (a *gormArticleDao) ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]dao.ArticleMode, error) {
	const ArticleStatusPublished = 2
	var arts []dao.ArticleMode
	err := a.db.WithContext(ctx).Where("updated_at <? AND status = ?", start, ArticleStatusPublished).Offset(offset).Limit(limit).First(&arts).Error
	return arts, err
}

func (a *gormArticleDao) GetPubById(ctx context.Context, id int64) (dao.PublishArticleModels, error) {
	var arts dao.PublishArticleModels
	err := a.db.WithContext(ctx).Model(&dao.PublishArticleModels{}).Where("id = ?", id).Find(&arts).Error
	return arts, err
}

func (a *gormArticleDao) GetByAuthor(ctx context.Context, author int64, offset, limit int) ([]dao.ArticleMode, error) {
	var arts []dao.ArticleMode
	err := a.db.WithContext(ctx).Model(&dao.ArticleMode{}).Where("author_id = ?", author).Offset(offset).Limit(limit).
		Order("updated_at DESC, created_at ASC").Find(&arts).Error
	return arts, err
}

func (a *gormArticleDao) UpdateById(ctx context.Context, d dao.ArticleMode) error {
	//map[string]any{}其中键表示要更新的列名，值表示要更新的对应值。这种用法使得我们可以根据需要更新的字段动态构建更新语句
	res := a.db.WithContext(ctx).Model(&dao.ArticleMode{}).Where("id = ? AND author_id = ? ", d.ID, d.AuthorId).
		Updates(map[string]any{
			"title": d.Title,
			//"author_id":    d.AuthorId, // 创作者不能修改
			"summary":      d.Summary,
			"content":      d.Content,
			"content_type": d.ContentType,
			"cover":        d.Cover,
			"status":       d.Status,
			"updated_at":   time.Now(),
		})
	err := res.Error
	if err != nil {
		return err
	}
	if res.RowsAffected == 0 {
		return errors.New("更新数据库失败")
	}
	return nil
}

func (a *gormArticleDao) Insert(ctx context.Context, d dao.ArticleMode) (int64, error) {
	err := a.db.WithContext(ctx).Create(&d).Error
	//写入数据库之后&d，数据更新了，然后可以返回article——id及文章表格的主键id
	return int64(d.ID), err
}

func (a *gormArticleDao) Sync(ctx context.Context, d dao.ArticleMode) (int64, error) {
	//使用事务进行同步两个库的操作，事务函数里面的匿名闭包函数，调用外面的变量，trancation事务管理这个生命周期
	//id需要定义一个局部变量，可以在匿名函数之后返回值
	var id = int64(d.ID)
	err := a.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		//需要使用事务的匿名函数tx *gorm.DB的数据库连接进行数据库操作，需要重新实例化一个数据库对象，确保在同一个上下文操作
		//先更新制作库
		txDao := NewGormArticleDao(tx)
		var err error
		if id > 0 {
			err = txDao.UpdateById(ctx, d)
		} else {
			id, err = txDao.Insert(ctx, d)
		}
		if err != nil {
			return err
		}
		//更新发布库
		err = txDao.Upsert(ctx, dao.PublishArticleModels{d})
		return err
	})
	return id, err
}

func (a *gormArticleDao) Upsert(ctx context.Context, d dao.PublishArticleModels) error {
	//使用冲突OnConflict的意思是数据冲突了  用MySQL就只需要关注一个地方 DoUpdates，就是唯一索引冲突，存在就update，不存在就create，没有唯一索引就是主键索引
	err := a.db.WithContext(ctx).Clauses(clause.OnConflict{
		// 哪些列冲突了
		//Columns: []clause.Column{clause.Column{Name: "id"}},
		// 如果数据冲突了 就啥也不干
		//DoNothing: true,
		// 如果数据冲突了，并且符合Where条件，就会执行更新
		//Where:
		// MySQL只需要关心这个 其他SQL标准的就不一样啦！
		DoUpdates: clause.Assignments(map[string]interface{}{
			"title": d.Title,
			//"author_id":    d.AuthorId, // 创作者不能修改
			"summary":      d.Summary,
			"content":      d.Content,
			"content_type": d.ContentType,
			"cover":        d.Cover,
			"status":       d.Status,
			"updated_at":   time.Now(),
		}),
	}).Create(&d).Error // 最终生成的语句 INSERT xxx ON DUPLICATE KEY UPDATE xxx，ON DUPLICATE KEY UPDATE 语法允许在插入操作中处理唯一键或主键冲突，根据需要执行更新操作或不执行任何操作
	return err
}

func (a *gormArticleDao) SyncStatus(ctx context.Context, author, id int64, status uint8) error {
	//使用事务进行同步两个库的操作，事务函数里面的匿名闭包函数，调用外面的变量，trancation事务管理这个生命周期
	err := a.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		//需要使用事务的匿名函数tx *gorm.DB的数据库连接进行数据库操作，需要重新实例化一个数据库对象，确保在同一个上下文操作
		//先更新制作库
		res := tx.Model(&dao.ArticleMode{}).Where("id = ? AND author_id = ?", id, author).Updates(map[string]any{
			"status":     status,
			"updated_at": time.Now(),
		})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected != 1 {
			return fmt.Errorf("文章id：%d不存在，无法下架", id)
		}

		//更新发布库
		err := tx.Model(&dao.PublishArticleModels{}).Where("id = ? AND author_id = ?", id, author).Updates(map[string]any{
			"status":     status,
			"updated_at": time.Now(),
		}).Error
		return err
	})
	return err
}
